'''
该脚本的作用：
找到 ninja 构建目录，并使用 ninja 生成超大项目的 cc.json ，按照目录筛选只保留部分编译命令生成一个小的 compile_commands.json
筛选时同步去掉 ccache 命令头，因为不是所有的IDE工具导入 compile_commands.json 时都支持 ccache 开头，有的需要直接是编译命令。


使用指导
1. 保存这个脚本到代码仓根目录 https://gitcode.com/code4lala/oh-xts-automation/blob/master/tools/ccjson.py
2. 在代码仓根目录执行这个脚本 python ccjson.py


下边这个碰到报错了再处理不报错不用管这个
Unknown argument: '-fno-optimize-strlen'
删除clangd配合交叉编译链不支持的编译选项
编辑文件 ~/.config/clangd/config.yaml 没有的话就新建一个，追加下边两行，注意是yaml格式的
CompileFlags:
  Remove: ["-mthumb-interwork", "-fno-optimize-strlen", "-freorder-blocks-algorithm=simple", "-mtune=size", "-mno-small-data-limit=0", "-fno-aggressive-loop-optimizations"]
也可以直接复制这个 tools 文件夹里的 clangd 文件夹里的 config.yaml 到你自己机器上 ~/.config/clangd/config.yaml 替换


下边这个用来配置vscode中clangd调用交叉编译链查询引用头文件目录，不是clang系列的编译器时，比如arm-none-eabi-gcc或者riscv32-unknown-elf-gcc等等，才需要配置这个，是clang系列编译器不用管这个
教程链接 https://clangd.llvm.org/troubleshooting#cant-find-standard-library-headers-map-stdioh-etc
.vscode/settings.json
{
    "clangd.path": "/absolute/path/to/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04/bin/clangd",
    "clangd.arguments": [
        "--query-driver=/absolute/path/to/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc"
    ]
}

变更记录：
v1.3：通过command模式改成arguments模式解决clangd解析复杂宏时解析错误的问题。老版本clangd对command的支持不太好，新版本clangd没有这个问题，新版本clangd用command、arguments都能正确解析，为提升兼容性，采用arguments模式输出
v1.2：import失败的时候才安装pip依赖，不用每次都提示用户选择，优化工具提示
v1.1：目录从输入变成从repo manifest获取后让用户选择
v1.0第一版：把所有步骤都集成到python脚本里依次执行
'''

import os

'''
依赖说明： ijson 因为json文件太大了直接加载解析会爆内存，用ijson可以流式处理边加载边解析，防止爆内存。
依赖说明： tqdm 进度条
依赖说明： questionary 交互式选择构建目录
'''

try:
    import json, shlex, ijson, time, subprocess, questionary, xml.etree.ElementTree as ET
    from tqdm import tqdm
except ModuleNotFoundError:
    x = input("先安装pip依赖，华为内网（黄区等）请选1，蓝区请选2，直接敲回车或其他输入跳过安装：")
    if x == "1":
        os.system('python -m pip install --index-url https://mirrors.tools.huawei.com/pypi/simple --trusted-host mirrors.tools.huawei.com ijson tqdm questionary')
    elif x == "2":
        os.system('python -m pip install --index-url https://repo.huaweicloud.com/repository/pypi/simple --trusted-host repo.huaweicloud.com ijson tqdm questionary')
    else:
        print('跳过安装pip依赖')

import json, shlex, ijson, time, subprocess, questionary, xml.etree.ElementTree as ET
from tqdm import tqdm

encodings = ['latin1', 'UTF-8']

def find_build_dirs():
    result = subprocess.run(['find', 'out', '-maxdepth', '3', '-name', 'build.ninja', '-type', 'f'], capture_output=True, text=True)
    if result.returncode != 0: raise Exception("查找编译目录失败，你是不是没编译过？要先触发一下编译，不用等编译跑完，屏幕上有打印[OHOS INFO] [NINJA]的时候就可以执行这个python脚本了")
    dirs = list(set([os.path.dirname(line.strip()) for line in result.stdout.split('\n') if line.strip()]))
    return dirs

def select_build_dir(dirs):
    if len(dirs) == 1: return dirs[0]
    return questionary.select("上下选择，回车确认：手机、PC、模拟器、系统组件、芯片组件、L0、L1、L2的产物目录都不一样，所以以实际为准，选择编译目录:", choices=dirs).ask()

def generate_cc_json(build_dir):
    cc_json_path = os.path.join(build_dir, "cc.json")
    if os.path.exists(cc_json_path):
        if not questionary.confirm(f"{cc_json_path}已存在，是否重新生成？直接回车不重新生成，按y重新生成（重新编译了才需要重新生成这个，没重新编译不需要重新生成，直接敲回车就行）", default=False).ask():
            return cc_json_path

    print(f"正在生成 {cc_json_path}...")
    start_time = time.perf_counter()
    with open(cc_json_path, 'w') as f:
        subprocess.run(['prebuilts/build-tools/linux-x86/bin/ninja', '-C', build_dir, '-w', 'dupbuild=warn', '-t', 'compdb'], stdout=f, check=True)
    end_time = time.perf_counter()
    print(f"生成{cc_json_path}耗时: {(end_time - start_time):.2f} 秒")
    return cc_json_path

def find_ccache_path(cc_json_path):
    # 逐行查找ccache路径，极速空间用了分布式缓存，分布式缓存工具替换了ccache工具为remote_proxy工具
    tools = ['ccache', 'remote_proxy']

    for encoding in encodings:
        try:
            with open(cc_json_path, 'r', encoding=encoding) as f:
                for line in f:
                    for tool in tools:
                        if '"command": "' in line and tool in line:
                            # 提取命令的第一个单词
                            parts = line.split('"command": "')
                            if len(parts) > 1:
                                cmd = parts[1].split()[0]
                                print(f"匹配到的编译命令前缀: {cmd}")
                                return cmd
            break
        except UnicodeDecodeError:
            continue

    raise Exception("未找到ccache或remote_proxy路径")

def filter_cc_json(json_name, ccache_name, select_dir):
    print(f'原始文件     = {json_name}')
    print(f'ccache路径   = {ccache_name}')
    print(f'筛选目录     = {select_dir}')

    new_json_dict = []
    total_count = 0

    # 编码猜测逻辑
    for encoding in encodings:
        try:
            print(f"尝试使用编码 {encoding} 读取并解析文件...")
            with open(json_name, 'r', encoding=encoding) as f:
                for comp_cmd in tqdm(ijson.items(f, 'item'), desc="处理条目", unit="条"):
                    total_count += 1
                    cmd = str(comp_cmd.get('command', ''))
                    file_path = str(comp_cmd.get('file', ''))
                    if (cmd.startswith(ccache_name + ' ') and select_dir in file_path):
                        # 改用 clangd 推荐的 arguments 数组方式，而不是 ninja 默认输出的 command 模式，解决复杂宏定义导致 clangd 解析错误导致 HksCheckGetKeyInfoListParams 函数无法跳转的问题
                        comp_cmd['arguments'] = shlex.split(cmd[len(ccache_name) + 1:])
                        del comp_cmd['command']
                        new_json_dict.append(comp_cmd)
            break
        except UnicodeDecodeError:
            print(f"使用编码 {encoding} 读取失败，使用下一种编码再试下...")
            new_json_dict = []
            total_count = 0
            continue

    print(f"原 {json_name} 中条目数量 {total_count}")
    print(f"筛选出来的条目数量 {len(new_json_dict)}")
    out_json_name = os.path.join(select_dir, 'compile_commands.json')
    with open(out_json_name, 'w', encoding='utf-8') as f:
        json.dump(new_json_dict, f, indent=4)
    print(f"完成写入 {out_json_name} !")

def main():
    build_dirs = find_build_dirs()
    build_dir = select_build_dir(build_dirs)
    cc_json_path = generate_cc_json(build_dir)
    ccache_path = find_ccache_path(cc_json_path)
    # 执行repo manifest命令并解析XML 提取所有path
    paths = sorted([project.get('path') for project in ET.fromstring(subprocess.check_output(['repo', 'manifest']).decode()).findall('project') if project.get('path')])
    select_dir = questionary.select('选择目录（上下键选择，可以直接搜索，举例：直接输入huks来搜索）:', choices=paths, use_search_filter=True, use_jk_keys=False).ask()
    start = time.perf_counter()
    filter_cc_json(cc_json_path, ccache_path, select_dir)
    end = time.perf_counter()
    print(f"筛选json运行时间： {(end - start):.2f} 秒")

    result = subprocess.run(['readlink', '-f', 'prebuilts/clang/ohos/linux-x86_64/llvm/bin/clangd'], capture_output=True, text=True)
    if result.returncode != 0: raise Exception("查找clangd目录失败")
    questionary.print('''
！！！！=================================================非常重要，用前必读========================================================！！！！
>>>刚才只是生成了json文件<<<，下一步是把json导入到你的vscode里，>>>光生成是没用的<<<，vscode并不会因此有任何变化，还有以下步骤要执行：
''', style="fg:ansiwhite bg:ansired")
    print(f'''
蓝区在 vscode 中导入 compile_commands.json 教程：
0. 先用vscode打开你刚才选择的代码仓目录{select_dir}文件夹，不要用vscode打开全仓目录。compile_commands.json已经生成到这个目录里了。
1. 卸载 Microsoft 的 C/C++ 插件
2. 安装 clangd 插件
3. 打开设置，搜索 clangd.path ，填写{result.stdout.strip()}
4. Ctrl + Shift + P，输入 restart ，选择 clangd: Restart language server 即可。

华为内网在 vscode-huawei 中导入 compile_commands.json 教程：
0. 先用vscode打开你刚才选择的代码仓目录{select_dir}文件夹，不要用vscode打开全仓目录。compile_commands.json已经生成到这个目录里了。
1. Ctrl + Shift + P，输入 import ，选择 Huawei C/C++: Import compile_commands.json files ，然后选择刚刚生成的 compile_commands.json 文件
2. 右下角点击 Huawei C/C++: Hybird 选择 Compiler 模式

有问题的话看 python 脚本上边注释里的说明。

当前版本： v1.3
最新版本获取： https://gitcode.com/code4lala/oh-xts-automation/blob/master/tools/ccjson.py
''')

if __name__ == '__main__':
    main()